package me.timlong.service.impl;

import me.timlong.dao.UserDOMapper;
import me.timlong.dao.UserPasswordDOMapper;
import me.timlong.dataobject.UserDO;
import me.timlong.dataobject.UserPasswordDO;
import me.timlong.error.BusinessException;
import me.timlong.error.EmBusinessError;
import me.timlong.service.UserService;
import me.timlong.service.model.UserModel;
import me.timlong.validator.ValidationResult;
import me.timlong.validator.ValidatorImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDOMapper userDOMapper;

    @Autowired
    private UserPasswordDOMapper userPasswordDOMapper;

    @Autowired
    private ValidatorImpl validator;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public UserModel getUserById(int id) {

        // 注意在互联网应用的开发过程中，不能把查询得到的UserDO数据直接返回到Controller层
        // service层必须有一个model的概念 ，这实际上是网页数据处理逻辑的实际模型
        // 而在dataobject层的model只是java程序对数据库中数据的简单映射
        UserDO userDO = this.userDOMapper.selectByPrimaryKey(id);

        if (null == userDO)
            return null;            // 用户不存在
        UserPasswordDO userPasswordDO = this.userPasswordDOMapper.selectByUserId(id);
        UserModel userModel = this.convertFromDataObject(userDO, userPasswordDO);
        return userModel;
    }

    @Override
    public UserModel getUserByIdInCache(int id) {
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get("user_validate_" + id);
        if(null == userModel) {
            userModel = this.getUserById(id);
            redisTemplate.opsForValue().set("user_validate_" + id, userModel);
            redisTemplate.expire("user_validate_" + id, 10, TimeUnit.MINUTES);
        }
        return userModel;
    }

    @Override
    @Transactional // 使得这部分代码要么都执行，要么都不执行，事务注解
    public void register(UserModel userModel) throws BusinessException {
        if (userModel == null)
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        // 信息不全则抛出异常
//        if(StringUtils.isEmpty(userModel.getName()) ||
//                null == userModel.getGender() ||
//                null == userModel.getAge() ||
//                StringUtils.isEmpty(userModel.getTelephone()) ||
//                StringUtils.isEmpty(userModel.getEncrptPassword())){
//            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
//        }

        // 这里对用户提交的注册信息的验证比较低级，繁琐，因此使用javax或者hibernate中常用的constraint工具进行相关类的验证
        // 通过注释的方式在Model类的字段上进行修饰限制类的值的条件，方便进行validate
        ValidationResult result = this.validator.validate(userModel);
        if (result.isHasErrors()) {
            // 有错
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
        }

        /*
         注意这里两次数据的插入应该是一个事务的关系，必须都插入成功或者都不能插入成功
         */
        // 将用户的信息插入数据库中
        // 实现将userModel转为UserDO的方法
        UserDO userDO = this.convertFromModel(userModel);

        // 我们注意到insert和insetSelective的区别，insert的作用就是不存在的直接为在数据控设为null,
        // 而insertSelective在数据传进去为空的时候，自动填充数据库中的默认值
        try {
            userDOMapper.insertSelective(userDO);  // 这里插入数据后将会把keyProperty返回给userDO，后面传递给userPassword表的外键
        } catch (DuplicateKeyException e) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "手机号已经存在");
        }
        userModel.setId(userDO.getId());
        UserPasswordDO userPasswordDO = this.convertPasswordFromModel(userModel);
        userPasswordDO.setUserId(userModel.getId());
        userPasswordDOMapper.insertSelective(userPasswordDO);

        return;
    }

    @Override
    public UserModel validateLogin(String telephone, String encrptPassword) throws BusinessException {

        // 通过用户的手机号获取用户的信息
        UserDO userDO = this.userDOMapper.selectByTelephone(telephone);
        if (null == userDO) {
            throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL);
        }
        UserPasswordDO userPasswordDO = this.userPasswordDOMapper.selectByUserId(userDO.getId());
        UserModel userModel = convertFromDataObject(userDO, userPasswordDO);

        // 比对密码和用户传入的密码是否一致
        if (!StringUtils.equals(encrptPassword, userModel.getEncrptPassword())) {
            throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL);
        }
        return userModel;
    }


    private UserPasswordDO convertPasswordFromModel(UserModel userModel) {
        if (null == userModel)
            return null;
        UserPasswordDO userPasswordDO = new UserPasswordDO();
        userPasswordDO.setEncrptPassword(userModel.getEncrptPassword());

        userPasswordDO.setUserId(userModel.getId());
        return userPasswordDO;
    }

    private UserDO convertFromModel(UserModel userModel) {
        if (null == userModel)
            return null;

        UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userModel, userDO);
        return userDO;
    }

    private UserModel convertFromDataObject(UserDO userDO, UserPasswordDO userPasswordDO) {
        UserModel userModel = new UserModel();
        // spring中的封装的方便的进行bean进行先关操作的方法
        BeanUtils.copyProperties(userDO, userModel);
        if (null != userPasswordDO)
            userModel.setEncrptPassword(userPasswordDO.getEncrptPassword());
        return userModel;
    }

}
